﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Minestorm;
using Minestorm.Retro.Entities;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Audio;

namespace Minestorm.Retro.GameLogic
{
    public enum MineType
    {
        Floating,
        Magnetic,
        Fireball,
        MagneticFireball
    }

    public class LevelSettings
    {
        public MineType[] MineTypes { get; set; }

        public int StarCount { get { return MineTypes.Length * 7; } }
    }

    public enum LevelState
    {
        Playing,
        Finished_Dead,
        Finished_Win,
        GameOver
    }

    public enum LevelPhase
    {
        Deploy,
        Spawn,
        Play,
        Dead,
        Finished
    }

    public class Level
    {
        private Game _game;
        private GraphicsDevice _graphics;
        private BasicEffect _effect;
        private LevelStateManager _state;
        private SpriteBatch _spriteBatch;
        private SpriteBatch _bulletSpriteBatch;
        private LevelBounds _bounds;
        private SpriteFont _font;
        private TimeSpan? _levelStart;
        private Texture2D _bulletTexture;

        private RenderTarget2D[] _renderTargets = new RenderTarget2D[50];
        private RenderTarget2D _screenRenderTarget;
        private Texture2D _lifeTexture;
        private int _currentRenderTarget = 50;
        private bool _initializedRenderTargets = false;
        private int _lives = 4;
        private SoundEffectInstance _introSound;

        private int _level;
        private bool _deployed = false;

        public static Matrix View = Matrix.CreateLookAt(
                new Vector3(0.0f, 0.0f, 50),
                Vector3.Zero,
                Vector3.Up
            );

        public static Matrix Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.01f, 100f);

        public RenderTarget2D ScreenRenderTarget { get { return _screenRenderTarget; } }

        public int Score { get { return _state.Score; } }

        public int Lives { get { return _lives; } }

        public Level(ScreenManager screenManager, int lives, int score, int level, LevelSettings settings, TextureManager textureManager, SoundManager soundManager, ContentManager contentManager)
        {
            _game = screenManager.Game;
            _graphics = _game.GraphicsDevice;
            _effect = new BasicEffect(_graphics);
            _state = new LevelStateManager(screenManager, score, settings, textureManager, soundManager);
            _spriteBatch = new SpriteBatch(_graphics);
            _bulletSpriteBatch = new SpriteBatch(_graphics);
            _font = contentManager.Load<SpriteFont>("Retro\\retro_font");
            _level = level;
            _lives = lives;

            _bounds = new LevelBounds(_graphics);
            _bounds.Initialize();

            LevelState = GameLogic.LevelState.Playing;
                
            var pp = _graphics.PresentationParameters;
            var format = pp.BackBufferFormat;

            var width = pp.BackBufferWidth;
            var height = pp.BackBufferHeight;

            for (var i = 0; i < _renderTargets.Length; i++)
                _renderTargets[i] = new RenderTarget2D(_graphics, width, height, false, format, pp.DepthStencilFormat, pp.MultiSampleCount, RenderTargetUsage.PreserveContents);

            _screenRenderTarget = new RenderTarget2D(_graphics, width, height, false, format, pp.DepthStencilFormat, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
            
            _lifeTexture = contentManager.Load<Texture2D>("Retro\\life");
            _bulletTexture = contentManager.Load<Texture2D>("Retro\\star");

            _introSound = soundManager.GetSoundEffect("intro_sound").CreateInstance();
            _introSound.Play();
        }

        public void UnloadContent()
        {
            foreach (var renderTarget in _renderTargets)
                renderTarget.Dispose();

            _screenRenderTarget.Dispose();
        }
        
        public LevelState LevelState { get; private set; }
		public ScreenManager ScreenManager { get; set; }

        public void HandleInput(InputState input, GameTime gameTime)
        {
            PlayerIndex playerIndex;

            if (IsGameOver)
            {
                if (input.IsNewKeyPress(Keys.Enter, null, out playerIndex) || input.IsNewButtonPress(Buttons.A, null, out playerIndex))
                    LevelState = GameLogic.LevelState.GameOver;

                return;
            }

            if (_state.Phase != LevelPhase.Play && _state.Phase != LevelPhase.Spawn)
                return;

            var ship = _state.GetShip();

            if (ship == null || !ship.IsAlive)
                return;

            ship.IsFiring = false;

            var gamePadState = GamePad.GetState(PlayerIndex.One);
            var keyboardState = Keyboard.GetState();

            if (gamePadState.IsConnected)
                ship.Direction -= gamePadState.ThumbSticks.Left.X / 15f;

            if (keyboardState.IsKeyDown(Keys.Left) || keyboardState.IsKeyDown(Keys.A))
                ship.Direction += 0.066f;

            if (keyboardState.IsKeyDown(Keys.Right) || keyboardState.IsKeyDown(Keys.D))
                ship.Direction -= 0.066f;

            if (keyboardState.IsKeyDown(Keys.Up) || keyboardState.IsKeyDown(Keys.W) || gamePadState.Buttons.X == ButtonState.Pressed)
            {
                ship.Trust(1f);
                _state.PlayThrust();
            }
            else
            {
                ship.StopThrust();
                _state.StopThrust(true);
            }

            if (input.IsNewKeyPress(Keys.Down, null, out playerIndex) || input.IsNewKeyPress(Keys.S, null, out playerIndex) || input.IsNewButtonPress(Buttons.Y, null, out playerIndex))
                _state.Teleport(gameTime);
            
            ship.IsFiring = input.IsNewKeyPress(Keys.Space, null, out playerIndex) || input.IsNewButtonPress(Buttons.A, null, out playerIndex);
        }

        public void UpdateLevelStart()
        {
            if (_state.Phase == LevelPhase.Deploy)
                _state.UpdateDropship();

            _state.UpdateStars();
        }

        public void UpdateMainLevel(GameTime gameTime)
        {
            if (_levelStart == null)
                _levelStart = gameTime.TotalGameTime;

            if (_state.Phase == LevelPhase.Spawn)
                _state.SpawnParentMines();
            if (_state.Phase == LevelPhase.Dead)
                if (_lives > 0 && _state.CheckDeadTimer())
                {
                    LevelState = GameLogic.LevelState.Finished_Dead;
                    _state.Phase = LevelPhase.Finished;
                    return;
                }
            if (_state.Phase != LevelPhase.Finished)
                if (_state.CheckWin())
                {
                    _state.Phase = LevelPhase.Finished;
                        return;
                }

            if (_state.Phase == LevelPhase.Finished)
                if (_state.CheckWinTimer())
                {
                    LevelState = GameLogic.LevelState.Finished_Win;
                    _state.Phase = LevelPhase.Finished;
                    return;
                }

            if (_state.Phase != LevelPhase.Finished && _state.Phase != LevelPhase.Dead)
                _state.Fire();

            _state.CheckNewStars();
            _state.MoveMinelayer();
            _state.MoveShip();
            _state.MoveBullets();
            _state.MoveMines();
            _state.MoveFireballs();
            _state.UpdateExplosions();
        }

        public bool ShouldDisplayLevelText(GameTime gameTime)
        {
            if (_levelStart == null)
                return false;

            return gameTime.TotalGameTime - _levelStart < TimeSpan.FromMilliseconds(3000);
        }

        public void Update(GameTime gameTime)
        {
            _state.BeginUpdate(gameTime);

            if (!_deployed)
            {
                _state.LoadDeployment(gameTime);
                _deployed = true;
            }

            switch (_state.Phase)
            {
                case LevelPhase.Deploy:
                    UpdateLevelStart();
                    break;
                default:
                    UpdateMainLevel(gameTime);
                    break;
            }

            _state.UpdatePointSprites(gameTime);
        }

        public void DrawBounds()
        {
            _effect.View = View;
            _effect.Projection = Projection;

            _effect.VertexColorEnabled = true;
            _effect.TextureEnabled = false;

            _effect.World = _bounds.GetWorld();

            var rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            rasterizerState.FillMode = FillMode.Solid;
            _graphics.RasterizerState = rasterizerState;

            foreach (var pass in _effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                _bounds.Draw();
            }
        }

        public bool IsGameOver { get { return _state.Phase == LevelPhase.Dead && _lives == 0; } }

        private void InitializeRenderTargets()
        {
            if (_initializedRenderTargets)
                return;

            foreach (var renderTarget in _renderTargets)
            {
                _graphics.SetRenderTarget(renderTarget);
                _graphics.Clear(Color.Transparent);
            }

            _initializedRenderTargets = true;
        }

        public void BeginDraw()
        {
            _effect.View = View;
            _effect.Projection = Projection;

            _effect.VertexColorEnabled = true;

            var rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            rasterizerState.FillMode = FillMode.Solid;
            _graphics.RasterizerState = rasterizerState;

            _currentRenderTarget = (_currentRenderTarget + 1) % _renderTargets.Length;
            _graphics.SetRenderTarget(_renderTargets[_currentRenderTarget]);
            _graphics.Clear(Color.Transparent);
        }

        public void DrawEntities(GameTime gameTime)
        {
            foreach (var pass in _effect.CurrentTechnique.Passes)
                foreach (var entity in _state.GetAllEntities())
                    if (!entity.Hidden)
                    {
                        //if (entity is Bullet)
                        //{
                        //    var pos = _graphics.Viewport.Unproject(new Vector3(entity.Position, 0), projectionMatrix, viewMatrix, entity.GetWorld("bullet"));
                        //    _bulletSpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
                        //    //_bulletSpriteBatch.Draw(_bulletTexture, new Rectangle((int)pos.X - _bulletTexture.Width / 2, (int)pos.Y - _bulletTexture.Height, _bulletTexture.Width, _bulletTexture.Height), Color.White);
                        //    _bulletSpriteBatch.Draw(_bulletTexture, new Rectangle((int)pos.X, (int)pos.Y, 1, 1), Color.White);
                        //    _bulletSpriteBatch.End();
                        //}
                        //else
                        {
                            foreach (var indexBuffer in entity.IndexBuffers)
                            {
                                _graphics.SetVertexBuffer(entity.VertexBuffer);
                                _graphics.Indices = indexBuffer;

                                _effect.World = entity.GetWorld(indexBuffer.Name);

                                var texture = entity.GetTexture();

                                _effect.TextureEnabled = texture != null;
                                _effect.Texture = texture;

                                _effect.VertexColorEnabled = entity.DrawVertices;

                                pass.Apply();
                                entity.BasicEffect = _effect;
                                entity.Draw(gameTime);
                            }
                        }
                    }
        }

        public void DrawEntityTrails()
        {
            var alpha = 1f;

            var width = _graphics.Viewport.Width;
            var height = _graphics.Viewport.Height;

            for (var i = _renderTargets.Length - 1; i >= 0; i--)
            {
                var index = (i + _currentRenderTarget) % _renderTargets.Length;

                var renderTarget = _renderTargets[index];

                DrawFullscreenQuad(renderTarget, width, height, alpha);

                alpha *= .7f;
            }
        }

        public void DrawToBackBuffer()
        {
            DrawFullscreenQuad(_screenRenderTarget, _screenRenderTarget.Width, _screenRenderTarget.Height, 1f);
        }

        public void DrawUI(GameTime gameTime)
        {
            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

            if (ShouldDisplayLevelText(gameTime) && !IsGameOver)
            {
                var text = "MINE FIELD " + _level;
                var size = _font.MeasureString(text);

                _spriteBatch.DrawString(_font, text, new Vector2(_graphics.Viewport.Width / 2 - size.X / 2, _graphics.Viewport.Height / 2 - (size.Y / 2 + 20)), Constants.VectorColor);
            }

            if (IsGameOver)
            {
                var text = "GAME OVER";
                var size = _font.MeasureString(text);

                _spriteBatch.DrawString(_font, text, new Vector2(_graphics.Viewport.Width / 2 - size.X / 2, _graphics.Viewport.Height / 2 - (size.Y / 2 + 20)), Constants.VectorColor);
            }

            var scoreWidth = _font.MeasureString("000000").X;
            var actualWidth = _font.MeasureString(Score.ToString()).X;

            _spriteBatch.DrawString(_font, Score.ToString(), new Vector2(290 + scoreWidth - actualWidth, 35), Constants.VectorColor);

            for (var i = 0; i < _lives; i++)
            {
                var dest = new Rectangle(585 - (i * (_lifeTexture.Width + 5)), 430 - _lifeTexture.Height, _lifeTexture.Width, _lifeTexture.Height);

                _spriteBatch.Draw(_lifeTexture, dest, Color.White);
            }

            _spriteBatch.End();
        }

        public void Draw(GameTime gameTime)
        {
            InitializeRenderTargets();

            var existingRenderTargets = _graphics.GetRenderTargets();

            BeginDraw();

            DrawEntities(gameTime);

            _state.DrawPointSprites(gameTime);

            _graphics.SetRenderTarget(_screenRenderTarget);
            _graphics.Clear(Color.Transparent);

            DrawEntityTrails();

            _graphics.SetRenderTargets(existingRenderTargets);
            _graphics.Clear(Color.Black);

            DrawToBackBuffer();

            DrawUI(gameTime);
        }

        private void DrawFullscreenQuad(Texture2D texture, int width, int height, float alpha)
        {
            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
            _spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White * alpha);
            _spriteBatch.End();
        }

        internal void Pause()
        {
            if (_introSound.State == SoundState.Playing)
                _introSound.Pause();
            _state.StopThrust(true, false);
        }

        internal void Resume()
        {
            if (_introSound.State == SoundState.Paused)
                _introSound.Resume();
        }
    }
}
